/*
 * NPlot - A charting library for .NET
 * 
 * Transform2D.cs
 * Copyright (C) 2003-2006 Matt Howlett and others.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System.Drawing;

namespace NPlot
{
    /// <summary>
    /// Encapsulates functionality for transforming world to physical coordinates optimally.
    /// </summary>
    /// <remarks>The existence of the whole ITransform2D thing might need revising. Not convinced it's the best way.</remarks>
    public class Transform2D
    {
        /// <summary>
        /// Constructs the optimal ITransform2D object for the supplied x and y axes.
        /// </summary>
        /// <param name="xAxis">The xAxis to use for the world to physical transform.</param>
        /// <param name="yAxis">The yAxis to use for the world to physical transform.</param>
        /// <returns>An ITransform2D derived object for converting from world to physical coordinates.</returns>
        public static ITransform2D GetTransformer(PhysicalAxis xAxis, PhysicalAxis yAxis)
        {
            ITransform2D ret = null;

//			if (xAxis.Axis.IsLinear && yAxis.Axis.IsLinear && !xAxis.Axis.Reversed && !yAxis.Axis.Reversed)
//				ret = new FastTransform2D( xAxis, yAxis );
//			else 
//				ret = new DefaultTransform2D( xAxis, yAxis );

            ret = new DefaultTransform2D(xAxis, yAxis);

            return ret;
        }

        /// <summary>
        /// This class does world -> physical transforms for the general case
        /// </summary>
        public class DefaultTransform2D : ITransform2D
        {
            private readonly PhysicalAxis xAxis_;
            private readonly PhysicalAxis yAxis_;

            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="xAxis">The x-axis to use for transforms</param>
            /// <param name="yAxis">The y-axis to use for transforms</param>
            public DefaultTransform2D(PhysicalAxis xAxis, PhysicalAxis yAxis)
            {
                xAxis_ = xAxis;
                yAxis_ = yAxis;
            }

            /// <summary>
            /// Transforms the given world point to physical coordinates
            /// </summary>
            /// <param name="x">x coordinate of world point to transform.</param>
            /// <param name="y">y coordinate of world point to transform.</param>
            /// <returns>the corresponding physical point.</returns>
            public PointF Transform(double x, double y)
            {
                return new PointF(
                    xAxis_.WorldToPhysical(x, false).X,
                    yAxis_.WorldToPhysical(y, false).Y);
            }

            /// <summary>
            /// Transforms the given world point to physical coordinates
            /// </summary>
            /// <param name="worldPoint">the world point to transform</param>
            /// <returns>the corresponding physical point</returns>
            public PointF Transform(PointD worldPoint)
            {
                return new PointF(
                    xAxis_.WorldToPhysical(worldPoint.X, false).X,
                    yAxis_.WorldToPhysical(worldPoint.Y, false).Y);
            }
        }

        /// <summary>
        /// This class does highly efficient world->physical and physical->world transforms
        /// for linear axes.
        /// </summary>
        public class FastTransform2D : ITransform2D
        {
            private readonly PageAlignedPhysicalAxis xAxis_;
            private readonly PageAlignedPhysicalAxis yAxis_;

            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="xAxis">The x-axis to use for transforms</param>
            /// <param name="yAxis">The y-axis to use for transforms</param>
            public FastTransform2D(PhysicalAxis xAxis, PhysicalAxis yAxis)
            {
                xAxis_ = new PageAlignedPhysicalAxis(xAxis);
                yAxis_ = new PageAlignedPhysicalAxis(yAxis);
            }

            /// <summary>
            /// Transforms the given world point to physical coordinates
            /// </summary>
            /// <param name="x">x coordinate of world point to transform.</param>
            /// <param name="y">y coordinate of world point to transform.</param>
            /// <returns>the corresponding physical point.</returns>
            public PointF Transform(double x, double y)
            {
                return new PointF(
                    xAxis_.WorldToPhysicalClipped(x),
                    yAxis_.WorldToPhysicalClipped(y));
            }

            /// <summary>
            /// Transforms the given world point to physical coordinates
            /// </summary>
            /// <param name="worldPoint">the world point to transform</param>
            /// <returns>the corresponding physical point</returns>
            public PointF Transform(PointD worldPoint)
            {
                return new PointF(
                    xAxis_.WorldToPhysical(worldPoint.X),
                    yAxis_.WorldToPhysical(worldPoint.Y));
            }
        }
    }
}